﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Xml;
using QuailServices.QuailDataServiceReference;

namespace Quail
{
    /// <summary>
    /// Adds all the log entries into an XML document
    /// </summary>
    public class XmlLoggerFactory : ITaskFactory
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="XmlLoggerFactory"/> class.
        /// </summary>
        /// <param name="folder">The folder to store the xml</param>
        public XmlLoggerFactory(string folder)
        {
            OutputFolder = folder;
        }

        /// <summary>
        /// Gets or sets the output folder for the xml content
        /// </summary>
        public string OutputFolder
        {
            get; set;
        }

        /// <summary>
        /// Gets a value indicating whether the logger supports screenshots
        /// </summary>
        public bool SupportsScreenshot
        {
            get { return false; }
        }

        /// <summary>
        /// Disposes the object
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
        }


        /// <summary>
        /// Creates a root task object
        /// </summary>
        /// <param name="session">The test session</param>
        /// <param name="taskname">the task name</param>
        /// <returns>a new DumbLoggerTask</returns>
        public ITaskImplementor CreateRootTask(TestSession session, string taskname)
        {
            return new XmlLoggerTask(session, this, taskname);
        }

        /// <summary>
        /// Converts a string to a valid file name
        /// </summary>
        /// <param name="name">name of file</param>
        /// <returns>a file-system happy filename</returns>
        private static string MakeValidFileName(string name)
        {
            var chars = System.IO.Path.GetInvalidFileNameChars();
            string result = string.Empty;

            foreach (char c in name)
            {
                if (!chars.Contains(c))
                {
                    if (c == ' ')
                        result += '_';
                    else
                        result += c;
                }
            }

            return result;
        }

        /// <summary>
        /// Adds all the log entries into an XML document
        /// </summary>
        public class XmlLoggerTask : ITaskImplementor
        {
            private readonly XmlDocument _dom;
            private readonly XmlLoggerFactory _loggerFactory;
            private readonly Stopwatch _timer = new Stopwatch();
            private readonly TestSession _testSession;
            private readonly XmlLoggerTask _parentTask;
            private long _sequencer;

            /// <summary>
            /// Initializes a new instance of the <see cref="XmlLoggerTask"/> class.
            /// </summary>
            /// <param name="testsession">The test session.</param>
            /// <param name="factory">The XmlLoggerFactory</param>
            /// <param name="name">The task name.</param>
            internal XmlLoggerTask(TestSession testsession, XmlLoggerFactory factory, string name)
            {
                _testSession = testsession;
                _loggerFactory = factory;
                _dom = new XmlDocument();
                _dom.LoadXml("<TestSession/>");

                AppendAttr(_dom.DocumentElement, "Started", _testSession.CurrentTime.ToString());
                AppendAttr(_dom.DocumentElement, "SessionName", _testSession.SessionName);

                StartUp(_dom.DocumentElement, name);
            }

            /// <summary>
            /// Initializes a new instance of the <see cref="XmlLoggerTask"/> class.
            /// </summary>
            /// <param name="testsession">The test session.</param>
            /// <param name="parentTask">The parent task.</param>
            /// <param name="name">The task name.</param>
            internal XmlLoggerTask(TestSession testsession, XmlLoggerTask parentTask, string name)
            {
                _parentTask = parentTask;
                _loggerFactory = parentTask._loggerFactory;
                _testSession = testsession;
                _dom = _parentTask._dom;

                StartUp(parentTask.Node, name);
            }

            /// <summary>
            /// Gets the current Duration of the task
            /// </summary>
            public long Duration
            {
                get { return _timer.ElapsedMilliseconds; }
            }

            /// <summary>
            /// Gets the node for the task
            /// </summary>
            public XmlNode Node { get; private set; }

            /// <summary>
            /// Gets the parent TestSession object
            /// </summary>
            public TestSession TestSession
            {
                get
                {
                    return _testSession;
                }
            }

            /// <summary>
            /// Gets the next value in the sequence
            /// </summary>
            protected long NextSequence
            {
                get
                {
                    if (_parentTask == null)
                        return System.Threading.Interlocked.Increment(ref _sequencer);

                    return _parentTask.NextSequence;
                }
            }

            /// <summary>
            /// does nothing
            /// </summary>
            public void Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }

            protected virtual void Dispose(bool disposing)
            {
            }

            /// <summary>
            /// Adds an attribute to a node
            /// </summary>
            /// <param name="node">the node to add attribute to</param>
            /// <param name="name">name of the attribute</param>
            /// <param name="value">value for the attribute</param>
            public static void AppendAttr(XmlNode node, string name, string value)
            {
                var attr = node.OwnerDocument.CreateAttribute(name);
                attr.Value = value;
                node.Attributes.SetNamedItem(attr);
            }

            /// <summary>
            /// Adds the message to the xml
            /// </summary>
            /// <param name="state">The log state</param>
            /// <param name="message">The message</param>
            /// <param name="url">the url</param>
            public void AddImpl(LogStatus state, string message, string url)
            {
                lock (_dom)
                {
                    var node = Node.OwnerDocument.CreateElement("Entry");

                    AppendAttr(node, "Time", _testSession.CurrentTime.ToString());
                    AppendAttr(node, "Status", state.ToString());
                    AppendAttr(node, "Seq", NextSequence.ToString());
                    AppendTextNode(node, "Url", url ?? _testSession.CurrentUrl);

                    if (!string.IsNullOrEmpty(message))
                        AppendTextNode(node, "Message", message);

                    Node.AppendChild(node);
                }
            }

            /// <summary>
            /// Begins a new task
            /// </summary>
            /// <param name="testsession">The test session.</param>
            /// <param name="taskname">name of task</param>
            /// <returns>The new task</returns>
            public ITaskImplementor BeginNewTaskImpl(TestSession testsession, string taskname)
            {
                return new XmlLoggerTask(testsession, this, taskname);
            }

            /// <summary>
            /// Removes the node from the xml
            /// </summary>
            public void RollbackImpl()
            {
                lock (_dom)
                    Node.ParentNode.RemoveChild(Node);
            }

            /// <summary>
            /// Appends an attribute to the task
            /// </summary>
            /// <param name="attribute">name of the attribute</param>
            /// <param name="message">value of the attribute</param>
            public void AddAttributeImpl(string attribute, string message)
            {
                lock (_dom)
                    AppendAttr(Node, attribute, message);
            }

            /// <summary>
            /// Completes the task
            /// </summary>
            /// <param name="state">state of the task</param>
            public void FinishImpl(LogStatus state)
            {
                lock (_dom)
                {
                    AppendAttr(Node, "Duration", (Duration / 1000.0).ToString("0.00"));
                    AppendAttr(Node, "Status", state.ToString());
                }
            }

            /// <summary>
            /// Implements saving the screenshot (not yet implemented)
            /// </summary>
            /// <param name="state">The log state</param>
            /// <param name="filename">NAme of the screenshot file</param>
            /// <param name="message">Message to go with the screenshot</param>
            public void ScreenshotImpl(LogStatus state, string filename, string message)
            {
            }

            /// <summary>
            /// Writes the xml to disk
            /// </summary>
            public void FlushImpl()
            {
                WriteXml();
            }

            /// <summary>
            /// Finishes the logging session
            /// </summary>
            /// <param name="logger">The test logger</param>
            public void StopImpl(TestLogger logger)
            {
                // write the output xml
                lock (_dom)
                {
                    AppendAttr(_dom.DocumentElement, "Finished", TestSession.CurrentTime.ToString());
                    AppendAttr(_dom.DocumentElement, "Duration", (logger.Watch.ElapsedMilliseconds / 1000.0).ToString("0.00"));
                    AppendAttr(_dom.DocumentElement, "Status", logger.OverallState.ToString());
                }
            }

            /// <summary>
            /// Stores the network traffic.
            /// </summary>
            /// <param name="url">the url or name for the traffic</param>
            /// <param name="remoteControlUrl">the url or name for the traffic remote control host</param>
            /// <param name="urlCounter">the number of times this url has been hit in this test run</param>
            /// <param name="traffic">The traffic.</param>
            public void StoreNetworkTrafficImpl(string url, string remoteControlUrl, int urlCounter, int msUntilReady, List<NetworkTrafficData> traffic)
            {
            }

            public void StoreMonitorImpl(LogStatus state, Enum scenario, string message)
            {
            }

            /// <summary>
            /// Writes the xml to disk
            /// </summary>
            protected void WriteXml()
            {
                lock (_dom)
                {
                    var writer = new XmlTextWriter(
                        string.Format(
                            "{0}\\Test_{1}.xml",
                            _loggerFactory.OutputFolder,
                            MakeValidFileName(TestSession.SessionName)),
                        Encoding.Default);
                    try
                    {
                        _dom.WriteTo(writer);
                    }
                    finally
                    {
                        writer.Close();
                    }
                }
            }

            /// <summary>
            /// Appends text to a xml node
            /// </summary>
            /// <param name="parent">the xml node</param>
            /// <param name="nodename">the name of the text</param>
            /// <param name="message">the value of the text</param>
            protected static void AppendTextNode(XmlNode parent, string nodename, string message)
            {
                var ele = parent.OwnerDocument.CreateElement(nodename);
                ele.AppendChild(parent.OwnerDocument.CreateTextNode(message));
                parent.AppendChild(ele);
            }
            
            private void StartUp(XmlNode parentNode, string name)
            {
                lock (_dom)
                {
                    var node = _dom.CreateElement("Task");
                    parentNode.AppendChild(node);

                    AppendAttr(node, "Name", name);
                    AppendAttr(node, "Time", TestSession.CurrentTime.ToString());
                    AppendAttr(node, "Status", LogStatus.Pending.ToString());
                    AppendAttr(node, "Seq", NextSequence.ToString());

                    Node = node;
                    _timer.Start();
                }
            }
        }
    }
}
